<?php


/**
 * 文件上传基类.
 *
 * @author xiaoxia.xu <xiaoxia.xuxx@aliyun-inc.com>
 * @copyright ©2003-2103 phpwind.com
 * @license http://www.windframework.com
 *
 * @version $Id: AbstractWindUpload.php 3172 2011-11-24 07:57:52Z yishuo $
 */
abstract class AbstractWindUpload
{
    /**
     * 是否有错误产生
     *
     * @var bool
     */
    protected $hasError = false;

    /**
     * 错误信息.
     *
     * @var array
     */
    protected $errorInfo = array('type' => array(), 'size' => array(), 'upload' => array());

    /**
     * 允许的类型.
     *
     * @var array
     */
    protected $allowType = array(); //允许上传的类型及对应的大小，array(ext=>size);

    /**
     * 上传文件.
     *
     * @param string $saveDir     文件保存的目录
     * @param string $preFileName 文件保存的前缀
     * @param array  $allowType   允许的格式array(ext=>size) size单位为b
     *                            <code>
     *                            array(
     *                            'jpg' => 1024,
     *                            'gif => 1000,
     *                            </code>
     *
     * @return array 返回上传成功的文件
     */
    public function upload($saveDir, $preFileName = '', $allowType = array())
    {
        $this->setAllowType($allowType);
        $uploaddb = array();
        foreach ($_FILES as $key => $value) {
            if (is_array($value['name'])) {
                $temp = $this->multiUpload($key, $saveDir, $preFileName);
                $uploaddb[$key] = isset($uploaddb[$key]) ? array_merge((array) $uploaddb[$key], $temp) : $temp;
            } else {
                $uploaddb[$key][] = $this->simpleUpload($key, $saveDir, $preFileName);
            }
        }

        return 1 == count($uploaddb) ? array_shift($uploaddb) : $uploaddb;
    }

    /**
     * 多文件上传.
     *
     * 多个控件
     * 一个表单中拥有多个上传文件的控件
     *
     * @param string $key         文件的key
     * @param string $saveDir     文件保存的目录
     * @param string $preFileName 保存文件的前缀默认为空字串
     *
     * @return array 返回上传成功之后的文件信息
     */
    private function simpleUpload($key, $saveDir, $preFileName = '')
    {
        return $this->doUp($key, $_FILES[$key], $saveDir, $preFileName);
    }

    /**
     * 多文件上传.
     *
     * 多个控件
     * 一个表单中拥有多个上传文件的控件
     *
     * @param string $key         文件的key
     * @param string $saveDir     文件保存的目录
     * @param string $preFileName 保存文件的前缀默认为空字串
     *
     * @return array 返回上传成功之后的文件信息
     */
    private function multiUpload($key, $saveDir, $preFileName = '')
    {
        $uploaddb = array();
        $files = $_FILES[$key];
        $num = count($files['name']);
        for ($i = 0; $i < $num; $i++) {
            $one = array();
            $one['name'] = $files['name'][$i];
            $one['tmp_name'] = $files['tmp_name'][$i];
            $one['error'] = $files['error'][$i];
            $one['size'] = $files['size'][$i];
            $one['type'] = $files['type'][$i];
            if (!($upload = $this->doUp($key, $one, $saveDir, $preFileName))) {
                continue;
            }
            $uploaddb[] = $upload;
        }

        return $uploaddb;
    }

    /**
     * 执行上传操作.
     *
     * @param string $tmp_name 临时文件
     * @param string $filename 目的文件名
     *
     * @return bool
     */
    abstract protected function postUpload($tmp_name, $filename);

    /**
     * 返回是否含有错误.
     *
     * @return bool
     */
    public function hasError()
    {
        return $this->hasError;
    }

    /**
     * 返回错误信息.
     *
     * @param string $errorType 错误类型,可选参数为:
     *                          <ul>
     *                          <li>'type': 类型出错而不能上传的文件信息,</li>
     *                          <li>'size': 超过指定大小而上传失败的文件信息<li>
     *                          <li>'upload': 文件不能上传过程出现错误的文件信息</li>
     *                          </ul>默认为空，则返回所有上述类型的错误信息
     *
     * @return array
     */
    public function getErrorInfo($errorType = '')
    {
        return isset($this->errorInfo[$errorType]) ? $this->errorInfo[$errorType] : $this->errorInfo;
    }

    /**
     * 设置允许上传的类型.
     *
     * @param array $allowType 允许上传的格式配置
     */
    public function setAllowType($allowType)
    {
        $allowType && $this->allowType = $allowType;
    }

    /**
     * 检查文件是否允许上传.
     *
     * @param string $ext 文件的后缀
     *
     * @return bool 如果在允许的范围则返回true，否则返回false
     */
    protected function checkAllowType($ext)
    {
        $allowType = array_keys((array) $this->allowType);

        return $allowType ? in_array($ext, $allowType) : true;
    }

    /**
     * 检查上传文件的大小.
     *
     * @param string $type       文件的类型
     * @param string $uploadSize 上传文件的大小
     *
     * @return bool 如果上传文件超过指定允许上传的大小则返回false,否则返回true
     */
    protected function checkAllowSize($type, $uploadSize)
    {
        if ($uploadSize < 0) {
            return false;
        }
        if (!$this->allowType || !$this->allowType[$type]) {
            return true;
        }

        return $uploadSize < $this->allowType[$type];
    }

    /**
     * 获得文件名字.
     *
     * @param array  $attInfo     上传文件的信息
     * @param string $preFileName 文件的前缀
     *
     * @return string 上传文件的名字
     */
    protected function getFileName($attInfo, $preFileName = '')
    {
        $fileName = mt_rand(1, 10).time().substr(md5(time().$attInfo['attname'].mt_rand(1, 10)), 10, 15).'.'.$attInfo['ext'];

        return $preFileName ? $preFileName.$fileName : $fileName;
    }

    /**
     * 获得保存路径.
     *
     * @param string $fileName 保存的文件名字
     * @param string $saveDir  保存文件的路径
     *
     * @return string 上传后的保存文件的完整路径
     */
    protected function getSavePath($fileName, $saveDir)
    {
        return $saveDir ? rtrim($saveDir, '\\/').'/'.$fileName : $fileName;
    }

    /**
     * 判断是否有上传文件.
     *
     * @param string $tmp_name 临时上传文件
     *
     * @return bool 如果该文件可以被上传则返回true，否则返回false
     */
    protected function isUploadFile($tmp_name)
    {
        if (!$tmp_name || $tmp_name == 'none') {
            return false;
        } elseif (function_exists('is_uploaded_file') && !is_uploaded_file($tmp_name) && !is_uploaded_file(str_replace('\\\\', '\\', $tmp_name))) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 初始化上传的文件信息.
     *
     * @param string $key         上传文件的key
     * @param string $value       上传文件的信息
     * @param string $preFileName 上传文件的前缀
     * @param string $saveDir     上传文件保存路径
     *
     * @return array 返回文件上传的信息
     */
    protected function initUploadInfo($key, $value, $preFileName, $saveDir)
    {
        $arr = array('attname' => $key, 'name' => $value['name'], 'size' => $value['size'], 'type' => $value['type'], 'ifthumb' => 0, 'fileuploadurl' => '');
        $arr['ext'] = strtolower(substr(strrchr($arr['name'], '.'), 1));
        $arr['filename'] = $this->getFileName($arr, $preFileName);
        $arr['fileuploadurl'] = $this->getSavePath($arr['filename'], $saveDir);

        return $arr;
    }

    /**
     * 判断是否使图片，如果使图片则返回.
     *
     * @param string $ext 文件后缀
     *
     * @return bool 如果该文件允许被上传则返回true，否则返回false
     */
    protected function isImage($ext)
    {
        return in_array($ext, array('gif', 'jpg', 'jpeg', 'png', 'bmp', 'swf'));
    }

    /**
     * 执行上传操作.
     *
     * @param string $key         上传文件的Key值
     * @param array  $value       文件的上传信息
     * @param string $saveDir     上传文件的保存路径
     * @param string $preFileName 上传文件的前缀
     *
     * @return array 上传成功后的文件信息
     */
    protected function doUp($key, $value, $saveDir, $preFileName)
    {
        if (!$this->isUploadFile($value['tmp_name'])) {
            return array();
        }
        $upload = $this->initUploadInfo($key, $value, $preFileName, $saveDir);

        if (empty($upload['ext']) || !$this->checkAllowType($upload['ext'])) {
            $this->errorInfo['type'][$key][] = $upload;
            $this->hasError = true;

            return array();
        }
        if (!$this->checkAllowSize($upload['ext'], $upload['size'])) {
            $upload['maxSize'] = $this->allowType[$upload['ext']];
            $this->errorInfo['size'][$key][] = $upload;
            $this->hasError = true;

            return array();
        }
        if (!($uploadSize = $this->postUpload($value['tmp_name'], $upload['fileuploadurl']))) {
            $this->errorInfo['upload'][$key][] = $upload;
            $this->hasError = true;

            return array();
        }
        $upload['size'] = intval($uploadSize);

        return $upload;
    }
}
